package exec

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/contract/native/manage/common"
	execcomm "hundsun.com/hsl/hschain/executor/common"
	"hundsun.com/hsl/hschain/protos/execute"
	protosledger "hundsun.com/hsl/hschain/protos/ledger"
	protosmanage "hundsun.com/hsl/hschain/protos/manage"
)

func init() {
	RegisterExecution(common.VoteExecName, NewVoteExecution)
	RegisterExecution(common.VotePreExecName, NewVotePreExecution)
}

// VoteExecution 投票节点实例
type VoteExecution struct {
	ExecutionAdaptor
}

// NewVoteExecution 新建投票执行逻辑
func NewVoteExecution(id, step uint32, proposal *protosmanage.ManageFlowProposal, properties *protosmanage.ExecProperties) Execution {
	var exec = &VoteExecution{}
	exec.InitAdaptor(id, step, proposal, properties)
	return exec
}

// Run 运行
func (e *VoteExecution) Run(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction) error {
	tx := gtx.GetTx()

	log.Infof("receive vote msg from %s, vote for flow[%d]", tx.GetFrom(), e.GetFlowID())
	// 投票人是否为属于委员会或者子委员会

	// 加载历史投票信息
	validatorKey := common.GenVoteHistoryKey(e.GetFlowID())

	validatorRWStatus := &execute.RWStatus{Key: validatorKey, RwStatus: execute.RwStatusType_ReadStatus}
	gtx.VarsRWStatus = append(gtx.VarsRWStatus, validatorRWStatus)

	validatorBytes, err := ctx.Get(validatorKey)
	if err != nil && err != types.ErrNotFound {
		gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_FAILED_OTHER_REASON
		log.Errorf("get validator info from db failed, err: %s", err.Error())
		return err
	}

	var validatorInfo = &protosmanage.ManageFlowValidatorInfo{}

	// 第一次投票，没有历史记录
	if len(validatorBytes) > 0 {
		err = proto.Unmarshal(validatorBytes, validatorInfo)
		if err != nil {
			gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_FAILED_OTHER_REASON
			log.Errorf("unmarshal validator info failed, err: %s", err.Error())
			return err
		}
		// 判断是否重复投票
		for _, addr := range validatorInfo.Validators {
			if addr == tx.GetFrom() {
				gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_MANAGEFLOW_REPEATE_VOTE
				log.Errorf("receive repeated vote message")
				return common.ErrRepeatVoteMsg
			}
		}
	}

	validatorInfo.Validators = append(validatorInfo.Validators, tx.GetFrom())
	kv, err := common.VoteHistoryKV(e.GetFlowID(), validatorInfo)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_FAILED_OTHER_REASON
		return err
	}

	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
	gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &protosledger.Log{Ty: common.TyLogVoteHistory, Data: kv.Value})

	kvStatus := &execute.RWStatus{Key: validatorKey, RwStatus: execute.RwStatusType_WriteStatus}
	gtx.VarsRWStatus = append(gtx.VarsRWStatus, kvStatus)
	var pass bool
	if e.GetProperties() != nil && len(e.GetProperties().Conditions) > 0 {
		for _, cond := range e.GetProperties().Conditions {
			pass, err = e.checkVote(ctx, gtx, validatorInfo, cond)
			if err != nil {
				gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_MANAGEFLOW_CHECK_FAILED
				log.Errorf("check vote failed, err: %s", err.Error())
				return err
			}

			// 需要判断所有的condition
			if e.GetProperties().AgreeAll {
				continue
			}
		}
	}
	if !pass {
		return common.ErrNotEnoughVotes
	}

	log.Infof("vote for manage flow %d successfully", e.GetFlowID())
	return nil
}

func (e *VoteExecution) checkVote(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	validatorInfo *protosmanage.ManageFlowValidatorInfo, cond *protosmanage.ExecCondition) (bool, error) {
	switch cond.Type {
	case protosmanage.ConditionType_EqualConditionType:
		expectNum, err := strconv.Atoi(cond.ExpectVal)
		if err != nil {
			return false, err
		}
		if len(validatorInfo.Validators) == expectNum {
			return true, nil
		}
		return false, nil
	case protosmanage.ConditionType_PercentConditionType:
		if cond.ExpectPercent == 0 {
			log.Error("invalid expect percent")
			return false, common.ErrNilParam
		}

		validators, err := common.GetValidators(ctx, gtx, e.GetProposal().GetType(), true)
		if err != nil {
			return false, err
		}
		if validators != nil && len(validators.Validators) > 0 {
			totalValidator := len(validators.Validators)
			percent := float32(len(validatorInfo.Validators)) / float32(totalValidator)
			if percent >= cond.ExpectPercent {
				log.Infof("check vote successfully, got enough vote msg")
				return true, nil
			}
			log.Infof("check vote failed, only received %d vote msg", len(validatorInfo.Validators))
			return false, nil
		}
		return false, fmt.Errorf("validator info is nil")

	default:
		return false, fmt.Errorf("unknown condition type :%d", cond.Type)
	}
}

// VotePreExecution 投票前预制节点实例
type VotePreExecution struct {
	ExecutionAdaptor
}

// NewVotePreExecution 新建投票执行逻辑
func NewVotePreExecution(id, step uint32, proposal *protosmanage.ManageFlowProposal, properties *protosmanage.ExecProperties) Execution {
	var exec = &VotePreExecution{}
	exec.InitAdaptor(id, step, proposal, properties)
	return exec
}

// Run 运行
func (e *VotePreExecution) Run(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction) error {
	tx := gtx.GetTx()

	var pass bool
	var err error
	if e.GetProperties() != nil && len(e.GetProperties().Conditions) > 0 {
		for _, cond := range e.GetProperties().Conditions {
			log.Infof("begin to check vote pre, address: %s", tx.GetFrom())
			pass, err = e.checkVotePre(ctx, gtx, cond, tx.GetFrom())
			if err != nil {
				gtx.TxExecRst.Receipt.TxStatus = protosledger.TransactionReceipt_MANAGEFLOW_INSUFFICIENT_PERMISSION
				log.Errorf("check vote pre failed, err: %s", err.Error())
				return common.ErrExecutionNotPassed
			}

			// 需要判断所有的condition
			if e.GetProperties().AgreeAll {
				continue
			}
		}
	}
	if !pass {
		log.Error("vote pre execution not pass.")
		return common.ErrExecutionNotPassed
	}
	log.Infof("check vote pre successfully")
	return nil
}

func (e *VotePreExecution) checkVotePre(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction, cond *protosmanage.ExecCondition, fromAddr string) (bool, error) {
	validators, err := common.GetValidators(ctx, gtx, e.GetProposal().GetType(), true)
	if err != nil {
		log.Errorf("get validator info failed, err: %s", err.Error())
		return false, err
	}

	if validators != nil {
		switch cond.Type {
		case protosmanage.ConditionType_EqualConditionType:
			if len(cond.ExpectVal) > 0 {
				if fromAddr == cond.ExpectVal {
					return true, nil
				}
				return false, nil
			}

			if len(validators.Validators) > 1 {
				return false, fmt.Errorf("genesis validator is more than 1, please use ExistConditionType")
			}
			if fromAddr == validators.Validators[0] {
				return true, nil
			}

			return false, errors.New("check validator before vote failed")
		case protosmanage.ConditionType_ExistConditionType:
			for _, val := range validators.Validators {
				if fromAddr == val {
					return true, nil
				}
			}
			return false, fmt.Errorf("from addr %s not existed in expect addresses %v", fromAddr, validators.Validators)
		default:
			return false, fmt.Errorf("check vote failed, unknown condition type :%d", cond.Type)
		}
	}
	return false, errors.New("check vote failed, validator is nil")
}
